package cn.bbstone.pisces2.server.fli;

import cn.bbstone.pisces2.comm.Const;
import cn.bbstone.pisces2.comm.fli.FliSavePoint;
import cn.bbstone.pisces2.config.Config;
import cn.bbstone.pisces2.server.cache.ServerCache;
import cn.bbstone.pisces2.util.BFileUtil;
import cn.bbstone.pisces2.util.CtxUtil;
import cn.bbstone.pisces2.util.FLIUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * fli.idx with fields(separator: -|:|-)
 * flieNo-|:|-fileCat-|:|-checksum-|:|-rpath
 * <p>
 * will ignore file which is symbol links
 */
public class FliBuilder {
    private static Logger log = LoggerFactory.getLogger(FliBuilder.class);

    // write cache
    public static final int batchSize = 8 * 1024;
    private static int count = 0;
    private static int batch = 0;


    /**
     * directly write index item to default fli.idx file(~/.fli_server/fli.idx)
     *
     * @param rootPath
     * @param filter
     * @return - fli.idx total lines number
     */
    public static int buildServerFliIndex(String rootPath, List<String> filter) {
        long start = System.currentTimeMillis();
        if (Files.notExists(Paths.get(rootPath))) {
            log.warn("not found file/directory: {}", rootPath);
        }

        // delete file list index file first
        try {
            Files.delete(Paths.get(FLIUtil.getServerIndexPath()));
        } catch (IOException e) {
            log.error("delete server fli index file error(before update it)", e);
        }
        // reset file list index counter
        count = 0;
        batch = 0;
        List<String> idxItemList = new ArrayList<>(batchSize);
        // do build process
        try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(FLIUtil.getServerIndexPath()), StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
            File file = new File(rootPath);
            if (Files.isDirectory(Paths.get(rootPath))) {
                // do recursively
                buildIndex(file, filter, writer, idxItemList);
            } else { // specified server root path is file (not directory), this is unexpected(should not happen).
                String relativePath = FLIUtil.getRelativePath(file.getAbsolutePath(), BFileUtil.getServerDir());
                if (!isFilter(relativePath)) {
                    count++;
                    // line data like: lineNum,D/F,/workdir/test/pisces/bin/client.bat
                    writer.write(String.format("%d,%s,%s", count, determineFileCat(file), relativePath));
                    writer.newLine();
                }
            }
            // the last traverse fli items amount not meet batch size, should write to fli.idx too.
            writeServerIndexCache(writer, idxItemList);
        } catch (IOException ioe) {
            log.error("build server fli index file error", ioe);
        }
        //
        long idxEnd = System.currentTimeMillis();
        log.debug("server index file updated, cost: {} ms.", (idxEnd - start));
        FliSavePoint fliSavePoint = new FliSavePoint();
        fliSavePoint.setCount(count);
        fliSavePoint.setChecksum(BFileUtil.checksum(FLIUtil.getServerIndexPath()));
        fliSavePoint.setFileNo(-1); // field in server fli.sp invalid
        fliSavePoint.setUpdateTime(System.currentTimeMillis());
        FLIUtil.writeServerSavePoint(fliSavePoint);
        // cache save point info
        ServerCache.setFliSavePoint(fliSavePoint);
        //
        log.debug("server savepoint file updated, cost: {} ms.", (System.currentTimeMillis() - idxEnd));
        log.debug("build server fli index total cost: {} ms.", (System.currentTimeMillis() - start));
        return count;
    }

    private static void buildIndex(File file, List<String> filter, BufferedWriter writer, List<String> idxItemList) throws IOException {
        File[] flist = file.listFiles();
        Arrays.sort(flist);
        for (File subfile : flist) {
            String relativePath = FLIUtil.getRelativePath(subfile.getAbsolutePath(), BFileUtil.getServerDir());
            if (!isFilter(relativePath)) {
                count++;
                idxItemList.add(String.format("%d%s%s%s%s",
                        count,
                        Const.FLI_FIELD_SEPARATOR,
                        determineFileCat(subfile),
                        Const.FLI_FIELD_SEPARATOR,
                        relativePath));
                if (idxItemList.size() > 0 && idxItemList.size() % batchSize == 0) {
                    writeServerIndexCache(writer, idxItemList);
                }
            }

            if (subfile.isDirectory()) {
                buildIndex(subfile, filter, writer, idxItemList);
            }
        }
    }

    private static String determineFileCat(File file) {
        if (file.isDirectory()) return Const.BFILE_CAT_DIR;
        if (file.isFile()) return Const.BFILE_CAT_FILE;
        if (Files.isSymbolicLink(Paths.get(file.getAbsolutePath()))) {
            return Const.BFILE_CAT_SL;
        }
        return null;
    }

    private static void writeServerIndexCache(BufferedWriter writer, List<String> idxItemList) throws IOException {
        for (int i = 0; i < idxItemList.size(); i++) {
            writer.write(idxItemList.get(i));
            writer.newLine();
//            count++;
        }
        // clear cache
        if (idxItemList.size() > 0) batch++;
        idxItemList.clear();
        log.info("build index progress, wrote: {} batch, total: {} line.", batch, count);
    }

    // ------------------------ fli index ---------
    private static boolean isFilter(String rpath) {
        List<String> filterList = getFliFilter();
        if (CollUtil.isNotEmpty(filterList)) {
            for (String regexp : filterList) {
                // find rpath not match filter rules, keep it
                if (ReUtil.contains(regexp, rpath)) return true;
            }
        }
        return false;
    }


    public static List<String> getFliFilter() {
        String fliFilter = CtxUtil.getConfigModel().getFilter();
        if (StringUtils.isBlank(fliFilter)) {
            return null;
        }
        return StrUtil.splitTrim(fliFilter, Const.COMMA_STR);
    }


    public static void main(String[] args) {
        List<String> filter = Arrays.asList(".DS_Store", ".mvn");
        int size = buildServerFliIndex(BFileUtil.getServerDir(), filter);

//        String path = "/Users/bbstone/workdir/assets/assets_pro/neomind/blessing-deploy/bless";
//        log.info("fileType: {}", determineFileCat(new File(path)));

    }


}
